package jmpo.cls;

/**
 * Интерфейс - это специальная сущность в языке Java, которая позволяет 
 * определить набор методов для работы с некоторыми объектами.
 * Говорят, что Интерфейс, определяет абстрактный тип данных.
 */

 /**
  * Методы в интерфейсе автоматически являются абстрактными.
  * Ключевое слово abstract не требуется.
  * Методы в интерфейсе являются по умолчанию являются public.
  * В отличие от абстрактных классов интерфейсы не имеют изменяемого
  * состояния, потому они не имеют конструкторов.
  */
interface IntegerSet {
    boolean contains(long e);
}

final class Point implements IntegerSet {
    /**
     * Мы должны указать модификатор public, так как для интерфейсов 
     * нельзя указать метды с областью видимости пакета или protected.
     */
    @Override
    public boolean contains(long e) {
        return e == p;
    }

    Point(long p) {
        this.p = p;
    }

    private long p;
}

/**
 * Один класс может реализовывать несколько интерфейсов.
 */
interface Named {
    String getName();
}

final class NamedSegment implements Named, IntegerSet {
    @Override
    public String getName() { return name; }

    @Override
    public boolean contains(long e) {
        return max >= e && min <= e;
    }

    NamedSegment(String name, long min, long max) {
        this.name = name;
        this.max = max;
        this.min = min;
    }

    private String name;
    private long min, max;
}

/**
 * Начиная с Java 7 возможности интерфейсов расширились.
 * Стало возможным определять default методы.
 * Они позволяют расширять компоненты в новых весиях вашего продукта,
 * сохраняя обратную совместимость.
 */
interface Component {
    String draw();

    default String getName() {
        return Component.NAME;
    }

    /**
     * Данная константа неявно final и static
     */
    String NAME = "UNKNOWN";
}

/**
 * Старый компонент не знает про новый метод и не реализует его.
 */
final class OldComponent implements Component {
    @Override
    public String draw()  {
        return "OldComponentDraw";
    }
}

/**
 * Новый компонент переопределяет оба метода.
 */
final class NewComponent implements Component {
    @Override
    public String draw() {
        return "NewComponentDraw";
    }

    @Override
    public String getName() {
        return name;
    }

    NewComponent(String name) {
        this.name = name;
    }

    private String name;
}

public class InterfaceExample {
    private static boolean pointTest() {
        IntegerSet set = new Point(100);
        return set.contains(100) == true;
    }

    private static boolean namedSegmentTest() {
        var namedSegment = new NamedSegment("Segment", -10, 5);
        Named named = namedSegment; // неявное преобразование
        IntegerSet set = namedSegment;
        return set.contains(0) && named.getName().equals("Segment");
    }

    private static boolean oldComponentTest() {
        var oldComponent = new OldComponent();
        Component component = oldComponent;
        return component.draw().equals("OldComponentDraw")
            && component.getName().equals("UNKNOWN");
    }

    private static boolean newComponentTest() {
        var newComponent = new NewComponent("Name");
        Component component = newComponent;
        return component.draw().equals("NewComponentDraw")
            && component.getName().equals("Name");
    }

    private static void test(boolean expectedTrue) {
        if(expectedTrue) {
            System.out.println("Ok!");
            return;
        }
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        test(pointTest());
        test(namedSegmentTest());
        test(oldComponentTest());
        test(newComponentTest());
    }
}
